package com.heima.media.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.nacos.common.utils.IoUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.heima.common.aliyun.GreenImageScan;
import com.heima.common.aliyun.GreenTextScan;
import com.heima.common.dto.ResponseResult;
import com.heima.common.enums.AppHttpCodeEnum;
import com.heima.common.minio.MinIOService;
import com.heima.common.util.SensitiveWordUtil;
import com.heima.media.dto.ArticleDto;
import com.heima.media.dto.ContentDto;
import com.heima.media.dto.ImageDto;
import com.heima.media.entity.WmChannel;
import com.heima.media.entity.WmNews;
import com.heima.media.entity.WmSensitive;
import com.heima.media.entity.WmUser;
import com.heima.media.feign.ArticleClient;
import com.heima.media.service.*;
import org.apache.commons.io.IOUtils;
import org.redisson.api.RBlockingQueue;
import org.redisson.api.RDelayedQueue;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.MergedAnnotationPredicates;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.InputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class AuditServiceImpl implements IAuditService {

    @Autowired
    GreenTextScan textScan; // 阿里云文本审核

    @Autowired
    GreenImageScan imageScan; // 阿里云图片审核

    @Autowired
    IWmNewsService wmNewsService; // 自媒体图文内容信息表

    @Autowired
    MinIOService minIOService; // minio文件储存云

    @Autowired
    IWmUserService userService; // 自媒体用户信息表

    @Autowired
    IWmChannelService channelService; // 频道信息表

    @Autowired
    ArticleClient articleClient; // 远程调用(审核通过, 保存app端相关文章接口)

    @Autowired
    IWmSensitiveService sensitiveService; // 敏感词信息表

    @Autowired
    RedissonClient redissonClient; // 发送延迟任务(消息)
    /**
     *  //文章审核
     * @param news
     */
    @Override
    @Async // 当前方法需要使用线程来执行
    public void audit(WmNews news) {
        //0. 打印当前线程
        System.out.println(Thread.currentThread().getName() + "开始执行审核.............newsId = " + news.getId());
        //1. 判断当前文章的状态:0草稿, 1 提交(待审核), 2审核失败, 3人工审核, 4人工审核通过, 8审核通过(待发布), 9已发布


        // 如果状态是 8审核通过(待发布),或者4人工审核通过  开始进入文章发布
        if (news.getStatus() == 8 || news.getStatus() == 4){
            //判断是否到了发布时间
            if (news.getPublishTime().getTime() > System.currentTimeMillis()){
                // 发送延迟消息
                sendDelayMessage(news);
            } else {
                // 否则直接发布
                publishArticle(news);
            }
            // 干掉方法
            return;
        }

        //2. 如果状态是1 进入自动审核流程
        //3. 提取内容中的文本和图片 从WmNews中获取0无图文章,1单图文章,3多图文章
        Map<String, Object> map = getTextAndImageFromContent(news.getContent());
        //3.1 提取文本内容
        String text = (String) map.get("text");
        //3.2 提取图片内容
        List<String> image = (List<String>) map.get("image");

        // --新需求-- 自管理敏感词审核
        boolean selfResult = checkSelf(news, text);
        // 如果自管理敏感词没通过直接return
        if (!selfResult) return;

        //3.3 调用阿里云文本审核
        boolean textResult = checkText(news, text);
        //3.3.1 如果文本审核没通过直接return
        if (!textResult) return;
        //3.4 调用阿里云图片审核
        boolean imageResult = checkImage(news, image);
        //3.4.1 如果图片审核没通过直接return
        if (!imageResult) return;

        // 延迟发布任务(消息)
        // 判断如果还没有到发布时间,将文章状态改为: 8审核通过（待发布） 发送延迟任务(消息)
        if (news.getPublishTime().getTime() > System.currentTimeMillis()){
            //1. 设置状态为: 8审核通过（待发布）
            news.setStatus(8);
            //2. 调用(自媒体图文内容信息表) 完成修改状态
            wmNewsService.updateById(news);
            //3. 发送延时消息
            sendDelayMessage(news);
        } else {
            //4.调用文章服务接口保存文章, 发布文章
            publishArticle(news);
        }
        //5. 打印当前线程
        System.out.println(Thread.currentThread().getName() + "审核完成!!!!!!!!!!!!!!!!!!!!!!!");
    }

    /**
     * 发送延时消息(任务)
     * @param news
     */
    private void sendDelayMessage(WmNews news) {
        if (news.getPublishTime().getTime() > System.currentTimeMillis()){
            //1. 创建阻塞队列
            RBlockingQueue<String> blockingQueue = redissonClient.getBlockingQueue("article_publish");
            //2. 创建延迟队列
            RDelayedQueue<String> delayedQueue = redissonClient.getDelayedQueue(blockingQueue);
            //3. 发送延迟消息 参数为: 1发送的消息, 2延迟时间, 3时间单位
            //3.1 需要延迟的时间为, 发布时间 - 当前系统时间
            long time = news.getPublishTime().getTime() - System.currentTimeMillis();
            delayedQueue.offer(JSON.toJSONString(news), time, TimeUnit.MILLISECONDS);
            System.out.println("消息发送完成" + new Date());
        }
    }

    /**
     * --新需求-- 自管理敏感词审核  本地敏感词过滤
     * @param news
     * @param text
     * @return
     */
    private boolean checkSelf(WmNews news, String text) {
        //1. 第一次使用时将数据库中的敏感词查询出来, 进行map的   初始化,  如果size == 0 说明是第一次, 需要查询出来
        if (SensitiveWordUtil.dictionaryMap.size() == 0){
            //1.1 从数据库查询所有敏感词, 以前是这样写:  select sensitives from wm_sensitive
            //1.2 构建查询条件
            LambdaQueryWrapper<WmSensitive> query = new LambdaQueryWrapper<>();
            //1.3 指定选择哪一列
            query.select(WmSensitive::getSensitives);
            //1.4 敏感词信息表 去查询所有敏感词
            List<String> words = sensitiveService.listObjs(query, o -> o.toString());
            //1.5 初始化map
            SensitiveWordUtil.initMap(words);
        }
        //2. 匹配文本内容
        //2.1 审核标题 + 标签 + 内容
        String content = news.getTitle() + news.getLabels() + text;
        //2.2 调用进行审核
        Map<String, Integer> map = SensitiveWordUtil.matchWords(content);
        //2.3 判断结果 如果有数据 ( > 0 ) 说明审核没通过 检测到了敏感词
        if (map.size() > 0){
            // 设置状态 2审核未通过
            news.setStatus(2);
            // 获取所有的key
            Set<String> keys = map.keySet();
            // 标签 用 逗号 分隔
            String label = String.join(",", keys);
            // 审核失败原因
            news.setReason("自管理敏感词审核失败:" + label);
            //调用 自媒体图文内容信息表, 更新状态
            wmNewsService.updateById(news);
            //2.4 审核失败, 禁行
            return false;
        }
        //2.5 否则放行
        return true;
    }

    /**
     * 发布文章
     * @param news
     */
    private void publishArticle(WmNews news) {
        //1. 创建文章数据对象
        ArticleDto dto = new ArticleDto();
        //2. 设置文章数据
        BeanUtils.copyProperties(news, dto);
        //dto的id置空, copy数据会copy过来id
        dto.setId(null);
        // 获取作者id 查询用户
        WmUser wmUser = userService.getById(news.getUserId());
        // 设置作者id ,
        dto.setAuthorId(wmUser.getApAuthorId());
        // 设置作者名,
        dto.setAuthorName(wmUser.getName());
        //3. 查询频道信息
        WmChannel wmChannel = channelService.getById(news.getChannelId());
        // 设置频道名称
        dto.setChannelName(wmChannel.getName());
        // 设置文章布局 0无图无章, 1单图文章, 2多图文章 从自媒体图文内容信息表中获得type(文章布局)
        dto.setLayout(news.getType());
        // 设置文章标记:0普通文章,1热点文章,2置顶文章,3精品文章,4大V文章
        dto.setFlag(0);
        //4. 前端显示图片的格式: http://xxx.jpg,http://yyy.jpg
        //4.1 封面图片需要转换
        List<ImageDto> coverImages = JSON.parseArray(news.getImages(), ImageDto.class);
        //4.2 创建存放封面图片地址的集合
        List<String> list = new ArrayList<>();
        // 遍历 coverImages
        for (ImageDto coverImage : coverImages) {
            // 加入封面图片地址的集合
            list.add(coverImage.getUrl());
        }
        //4.3 将集合转换成 逗号 分割的字符串
        String join = String.join(",", list);
        //设置文章图片: 多张, 用逗号分隔
        dto.setImages(join);
        //5. 远程调用(审核通过, 保存app端相关文章)
        ResponseResult<Long> responseResult = articleClient.saveArticle(dto);
        //5.1 判断一下远程调用是否成功, 成功才返回数据,远程调用返回的状态码 与成功的状态码作对比
        if (responseResult.getCode().equals(AppHttpCodeEnum.SUCCESS.getCode())){
            // 从响应结果中获得数据
            Long articleId = responseResult.getData();
            // 将文章id写回到自媒体文章表中
            news.setArticleId(articleId);
            // 修改状态为: 9已发布
            news.setStatus(9);
            // 调用自媒体图文内容信息表更新状态
            wmNewsService.updateById(news);
        }
    }

    /**
     * 3.4 调用阿里云图片审核
     * @param news
     * @param image
     * @return
     */
    private boolean checkImage(WmNews news, List<String> image) {
        //1. 需要审核的图片包含内容图片和封面
        //2. 获取封面图片
        List<ImageDto> list = JSON.parseArray(news.getImages(), ImageDto.class);
        //2.1 遍历
        for (ImageDto imageDto : list) {
            //2.2 判断封面图片是否已经存在于内容图片中
            if (!image.contains(imageDto.getUrl())){
                //不包含就把封面图加入到最终要审核的图片集合中
                if (!StringUtils.isEmpty(imageDto.getUrl())){
                    //判断封面图片是否为空, 非空才加入眼神和图集
                    image.add(imageDto.getUrl());
                }
            }
        }
        //3. 判断最终要审核的图片集合是否有图片, 如果没有, 直接放行
        if (image.size() == 0){
        return true;
        }
        //4. 定义result 信号位
        boolean result = false;
        //5. 因为Minio服务没有外网地址, 所以需要先下载图片内容
        try {
            //5.1 构建需要审核图片的二进制格式集合
            List<byte[]> imageList = new ArrayList<>();
            //5.2 遍历图片集合, 到minio中下载
            for (String s : image) {
                // 远程调用minio下载图片
                InputStream inputStream = minIOService.download(s);
                // 将 InputStream 转换成字节数组
                byte[] bytes = IOUtils.toByteArray(inputStream);
                // 放入到二进制图片的集合中
                imageList.add(bytes);
            }
            //5.3 调用阿里云图片审核
            Map map = imageScan.imageScan(imageList);
            //6. 分析结果中的建议 suggestion
            result = suggestionResult(news, result, map);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 3.3 调用阿里云文本审核
     * @param news
     * @param text
     * @return
     */
    private boolean checkText(WmNews news, String text) {
        //1. 定义result 信号位
        boolean result = false;
        //2. 文本审核需要审核 标题+标签+正文中的文字
        String content = news.getTitle() + news.getLabels() + text;
        try {
            //3. 远程调用阿里云文本审核
            Map map = textScan.greenTextScan(content);
            //4. 分析结果中的建议 suggestion
            result = suggestionResult(news, result, map);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //5. 返回审核结果
        return result;
    }

    /**
     * 审核结果建议分析 suggestion
     * @param news
     * @param result
     * @param map
     * @return
     */
    private boolean suggestionResult(WmNews news, boolean result, Map map) {
        //3.1分析结果中的建议 suggestion
        String suggestion = (String) map.get("suggestion");
        //3.2 suggestion 的结果
        switch (suggestion){
            case "pass":
                //3.2.1 pass: 结果正常
                result = true;
                break;
            case "block":
                //3.2.2 block: 结果违规,修改文章状态为,2审核失败,并记录失败的原因
                news.setStatus(2);
                //获得审核失败原因: 从label中取, 如枪等......
                String label = (String) map.get("label");
                news.setReason("阿里云审核失败原因: " + label);
                //调用 自媒体图文内容信息表, 更新状态,记录原因
                wmNewsService.updateById(news);
                break;
            case "review":
                //3.2.3 review: 结果不确定,需要进行人工审核,修改文章状态为,3人工审核
                news.setStatus(3);
                //调用 自媒体图文内容信息表, 更新状态
                wmNewsService.updateById(news);
                break;
        }
        return result;
    }

    /**
     * 3. 提取内容中的文本和图片
     * @param content
     * @return
     */
    private Map<String, Object> getTextAndImageFromContent(String content) {
        //1. 定义返回结果
        Map<String, Object> map = new HashMap<>();
        //2. 定义文本内容
        StringBuilder sb = new StringBuilder();
        //3. 定义图片集合
        List<String> images = new ArrayList<>();
        //4. 将内容转换为ContentDto集合
        List<ContentDto> contentDtos = JSON.parseArray(content, ContentDto.class);
        //5. 遍历
        for (ContentDto contentDto : contentDtos) {
            if (contentDto.getType().equals("text")) {
                //5.1 如果是文本, 直接拼接
                sb.append(contentDto.getValue());
            } else {
                //5.2 否则添加到图片集合
                images.add(contentDto.getValue());
            }
        }
        //6. 文本内容放入返回结果集
        map.put("text", sb.toString());
        //7. 图片集合放入返回结果集
        map.put("image", images);
        //8. 返回提取内容中的文本和图片
        return map;
    }
}
